package com.beiji.lib.pen

import android.app.Application
import com.beiji.lib.pen.cache.PenDotCacheHelper
import com.beiji.lib.pen.cache.SharedPreferencesHelper
import com.beiji.lib.pen.constants.PenBusTag
import com.beiji.lib.pen.constants.PenConnectState
import com.beiji.lib.pen.constants.PenType
import com.beiji.lib.pen.device.IPen
import com.beiji.lib.pen.device.PenT111
import com.beiji.lib.pen.listener.IPenDataListener
import com.beiji.lib.pen.listener.OfflineDataListener
import com.beiji.lib.pen.model.BatteryLevelWrapper
import com.beiji.lib.pen.model.DotUnit
import com.beiji.lib.pen.model.PenInfo
import com.hwangjr.rxbus.RxBus
import com.tqltech.tqlpencomm.bean.Dot

import kotlinx.coroutines.android.UI
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.lang.ref.WeakReference
import java.util.concurrent.*

/**
 * 数码笔管理类
 * Created by X on 2018/6/13.
 */
class PenManager {

    companion object {
        val TAG: String = PenManager::class.java.simpleName
        val instance = PenManager()

        const val UPDATE_PEN_INFO_PERIOD = 1000 * 60L
        const val BATTERY_LEVEL_LOW_VALUE = 20

        private const val KEY_LAST_CONNECTED_PEN_ADDRESS = "key_last_connected_pen_address"
    }

    var batteryChangedCallback: ((Int) -> Unit)? = null

    var offlineDataCallback: OfflineDataListener? = null

    private var currentPen: IPen? = null

    //返回值为true则表示需要存储，false则丢弃
    private var penCallback: ((DotUnit) -> Boolean)? = null

    //sdk传出来的原始dot数据
    private var penDataCallback: ((Dot) -> Boolean)? = null

//    private var penCallbackList: MutableList<(DotUnit) -> Unit> = arrayListOf()

    private var updateInfoTaskTimer: ScheduledExecutorService = Executors.newScheduledThreadPool(2)

    private var updateInfoTask: ScheduledFuture<*>? = null

    //在连接成功页面和列表页直接通过书写的方式跳转新建笔记页面的时候，在笔记页面注册回调成功之前，需要把书写的点先缓存下来，等页面注册完成，再输出到页面上
    private var dotCacheQueue: LinkedBlockingQueue<DotUnit> = LinkedBlockingQueue()

    //TODO Test code
    private lateinit var application: WeakReference<Application>
    private var mPenConnectStateListener: PenConnectStateListener? = null

    fun setPenConnectStateListener(list: PenConnectStateListener) {
        this.mPenConnectStateListener = list
    }

    fun init(application: Application) {
        this.application = WeakReference(application)
        PenT111.instance.init(application)
        SharedPreferencesHelper.init(application)
        PenScanner.instance.init(application)
    }

    fun connectPen(name: String?, mac: String): Boolean {
        val penType = PenType.TQL_T111
        val pen = PenInfo(name, mac, penType)
        currentPen = when (penType) {
            PenType.TQL_T111 -> PenT111.instance
            else -> return false
        }

        if (currentPen == null) {
            return false
        }

        return with(currentPen!!) {
            penInfo = pen
            penDataListener = dataListener
            connect()
        }
    }

    fun getConnectedPen(): PenInfo? {
        return currentPen?.penInfo
    }

    fun getConnectState(): PenConnectState {
        if (currentPen == null || currentPen!!.penInfo == null || currentPen?.currentConnectState == PenConnectState.DISCONNECTED) {
            return PenConnectState.DISCONNECTED
        }
        return currentPen!!.getConnectState()
    }

    fun disConnect() {
        currentPen?.disConnect()
    }

    fun registerDataCallback(penCallback: (DotUnit) -> Boolean) {
        this.penCallback = penCallback
//        penCallbackList.clear()
//        this.penCallbackList.add(penCallback)
    }

    fun setPenDataListener(penDataCallback: (Dot) -> Boolean) {
        this.penDataCallback = penDataCallback
    }

    fun unRegisterDataCallback(penCallback: (DotUnit) -> Boolean) {
        if (this.penCallback == penCallback) {
            this.penCallback = null
        }
//        this.penCallbackList.remove(penCallback)
//        penCallbackList.clear()
    }

    fun removePenDataListener(penDataCallback: (Dot) -> Boolean) {
        if (this.penDataCallback == penDataCallback) {
            this.penDataCallback = null
        }
    }

    private val dataListener = object : IPenDataListener {

        override fun onPenConnectStateChanged(state: PenConnectState) {
            mPenConnectStateListener?.connectStateCallback(state)
            PenLog.e(state.toString())
            RxBus.get().post(PenBusTag.CONNECT_STATE, state)
            val penAddress = currentPen?.penInfo?.macAddress
            when (state) {
                PenConnectState.CONNECTING -> PenLog.e("connecting to $penAddress")
                PenConnectState.CONNECTED -> penAddress?.let {
                    cancelAutoConnectTask()
                    savePenAddress(it)
                    launch {
                        delay(5000, TimeUnit.MILLISECONDS)
                        startUpdatePenInfoTask(UPDATE_PEN_INFO_PERIOD)
                    }
                }
                PenConnectState.CONNECT_FAILED -> {
                    currentPen = null
                    PenLog.e("connect failed.")
                }
                PenConnectState.DISCONNECTED -> {
                    currentPen = null
                    PenLog.e("$penAddress disconnected.")
                    cancelUpdatePenInfoTask()
                    startAutoConnectTask()
                }
            }
        }

        override fun onBatteryLevelChanged(percent: Int) {
            PenLog.e(TAG, "batteryLevel -> $percent")
            //RxBus用Int
            RxBus.get().post(PenBusTag.BATTERY_ACHIEVED, BatteryLevelWrapper(percent))
            launch(UI) {
                batteryChangedCallback?.invoke(percent)
            }
        }

        override fun onReceiveDot(dot: DotUnit) {
            if (dot.force < 0) {
                launch(UI) {
                    application.get()?.toast("type is null")
                }
                return
            }
            launch(UI) {
                val isCaching = penCallback?.invoke(dot) ?: false
                if (isCaching) {
                    PenDotCacheHelper.cacheDot(dot)
                }
            }
//            PenDotCacheHelper.cacheDot(dot)
//            for (callback in penCallbackList) {
//                launch(UI) {
//                    callback(dot)
//                }
//            }
        }

        override fun onReceiveTqlDot(dot: Dot) {
            if (dot.force < 0) {
                launch(UI) {
                    application.get()?.toast("pen data is null")
                }
                return
            }
            launch(UI) {
                penDataCallback?.invoke(dot)
            }
        }

        override fun onOfflineDataCount(count: Int) {
            PenLog.i(TAG, "onOfflineDataCount -> $count")
            launch(UI) {
                offlineDataCallback?.onOfflineDataCount(count)
            }
        }

        override fun onOfflineDataDownloadProgress(progress: Int) {
            PenLog.i(TAG, "onOfflineDataDownloadProgress -> $progress")
            launch(UI) {
                offlineDataCallback?.onOfflineDataDownloadProgress(progress)
            }
        }

        override fun onOfflineDataReceive(dot: DotUnit) {
            PenDotCacheHelper.cacheDot(dot)

            PenLog.i(TAG, "onOfflineDataReceive -> $dot")
            launch(UI) {
                offlineDataCallback?.onOfflineDataReceive(dot)
            }
        }

        override fun onOfflineDataReceiveProgress(progress: Int) {
            PenLog.i(TAG, "onOfflineDataReceiveProgress -> $progress")
            launch(UI) {
                offlineDataCallback?.onOfflineDataReceiveProgress(progress)
            }
        }

    }

    private fun startUpdatePenInfoTask(period: Long) {
        updateInfoTask = updateInfoTaskTimer.scheduleAtFixedRate({ requestBatteryInfo() }, 10 * 1000, period, TimeUnit.MILLISECONDS)
    }

    private fun cancelUpdatePenInfoTask() {
        updateInfoTask?.cancel(true)
    }

    fun startAutoConnectTask() {
        if (getConnectState() == PenConnectState.CONNECTED
                || getConnectState() == PenConnectState.CONNECTING
                || getLastConnectedPenAddress().isNullOrEmpty()) {
            return
        }
        PenScanner.instance.startScan(scanPenCallBack)
    }

    fun cancelAutoConnectTask() {
        PenScanner.instance.stopScan()
    }

    private var scanPenCallBack = object : PenScanner.ScanPenCallBack {
        override fun onPenFound(pen: PenInfo) {
            if (pen.macAddress == getLastConnectedPenAddress()) {
                PenManager.instance.connectPen(pen.name, pen.macAddress)
            }
        }

        override fun onTimeOut() {}

    }

    private fun requestBatteryInfo() {
        currentPen?.requestPenInfo()
    }

    fun savePenAddress(address: String) {
        SharedPreferencesHelper.saveStringValue(KEY_LAST_CONNECTED_PEN_ADDRESS, address)
    }

    fun getLastConnectedPenAddress(): String? {
        return SharedPreferencesHelper.getStringValue(KEY_LAST_CONNECTED_PEN_ADDRESS)
    }

    fun cacheDotTemporary(dot: DotUnit) {
        dotCacheQueue.offer(dot)
    }

    fun drainCachedDots(cachedDotCallback: ((DotUnit) -> Unit)) {
        while (true) {
            val cachedDot = dotCacheQueue.poll()
            cachedDot ?: break
            cachedDotCallback.invoke(cachedDot)
        }
    }

    fun isPenLowBattery(): Boolean {
        val batteryLevel = getConnectedPen()?.batteryLevel ?: 0
        //为0表示还未获取到电量或者出错了
        if (batteryLevel == 0) {
            return false
        }
        return batteryLevel <= BATTERY_LEVEL_LOW_VALUE
    }

    fun getPenOfflineDataCount() {
        currentPen?.getPenOfflineDataCount()
    }

    fun downloadOfflineData() {
        currentPen?.downloadOfflineData()
    }

    fun stopOfflineData() {
        currentPen?.stopOfflineData()
    }

    fun removeOfflineData() {
        currentPen?.removeOfflineData()
    }


    interface PenConnectStateListener {
        fun connectStateCallback(state: PenConnectState)
    }
}
